#include "xlcomm/net/detail/channel.h"

#include <sys/epoll.h>

#include <sstream>

namespace xlcomm {
namespace net {

const int Channel::kNoneEvent = 0;
const int Channel::kReadEvent = EPOLLIN | EPOLLPRI;
const int Channel::kWriteEvent = EPOLLOUT;

Channel::Channel(EventLoop* loop, int fd)
    : loop_(loop),
      fd_(fd),
      interested_events_(0),
      triggered_events_(0),
      status_(kStatusInit),
      tied_(false),
      event_handling_(false),
      added_to_loop_(false) {
  XLCOMM_ASSERT(loop_ != nullptr);
}

Channel::~Channel() {
  XLCOMM_ASSERT(!added_to_loop_);  // 断言已经调了Remove
  XLCOMM_ASSERT(!event_handling_);  //断言在IO事件回调过程中Channel对象不会析构
  if (loop_->IsInLoopThread()) {
    XLCOMM_ASSERT(!loop_->HasChannel(this));
  }
}

void Channel::EnableReading() {
  LOG_TRACE << "Channel(fd:" << fd_ << ") " << __FUNCTION__;
  interested_events_ |= kReadEvent;
  Update();
}

void Channel::DisableReading() {
  LOG_TRACE << "Channel(fd:" << fd_ << ") " << __FUNCTION__;
  interested_events_ &= ~kReadEvent;
  Update();
}

void Channel::EnableWriting() {
  LOG_TRACE << "Channel(fd:" << fd_ << ") " << __FUNCTION__;
  interested_events_ |= kWriteEvent;
  Update();
}

void Channel::DisableWriting() {
  LOG_TRACE << "Channel(fd:" << fd_ << ") " << __FUNCTION__;
  interested_events_ &= ~kWriteEvent;
  Update();
}

void Channel::DisableAll() {
  LOG_TRACE << "Channel(fd:" << fd_ << ") " << __FUNCTION__;
  interested_events_ = kNoneEvent;
  Update();
}

void Channel::Update() {
  added_to_loop_ = true;
  loop_->UpdateChannel(this);
}

void Channel::Remove() {
  LOG_TRACE << "Channel(fd:" << fd_ << ") remove from poller";
  XLCOMM_ASSERT(IsNoneEvent());  // 断言已经调了DisableAll或Disable..其他监听
  added_to_loop_ = false;
  loop_->RemoveChannel(this);
}

void Channel::HandleEvent(Timestamp recv_time) {
  std::shared_ptr<void> shared;
  if (tied_) {
    shared = tie_.lock();
    if (shared) {  // 转换成功，说明对象还没销毁。持有、防止被销毁
      HandleEventWithShared(recv_time);
    }
  } else {
    HandleEventWithShared(recv_time);
  }
}

void Channel::HandleEventWithShared(Timestamp recv_time) {
  event_handling_ = true;
  LOG_TRACE << "Channel(fd:" << fd_
            << ") handle triggered events: " << TriggeredEventsToString();
  if ((triggered_events_ & EPOLLHUP) && !(triggered_events_ & EPOLLIN)) {
    LOG_TRACE << "Channel(fd:" << fd_ << ") call close_callback_";
    if (close_callback_) {
      LOG_INFO << "hang up happend, peer close connection";
      close_callback_();
    }
  }
  if (triggered_events_ & EPOLLERR) {
    LOG_TRACE << "Channel(fd:" << fd_ << ") call error_callback_";
    if (error_callback_) error_callback_();
  }
  if (triggered_events_ & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) {
    LOG_TRACE << "Channel(fd:" << fd_ << ") call read_callback_";
    if (read_callback_) read_callback_(recv_time);
  }
  if (triggered_events_ & EPOLLOUT) {
    LOG_TRACE << "Channel(fd:" << fd_ << ") call write_callback_";
    if (write_callback_) write_callback_();
  }
  event_handling_ = false;
}

void Channel::Tie(const std::shared_ptr<void>& obj) {
  tie_ = obj;
  tied_ = true;
}

std::string Channel::EventsToString(int events) {
  std::ostringstream oss;
  // available for read
  if (events & EPOLLIN) oss << "EPOLLIN ";
  // 1.out-of-band data; 2.. 3..
  if (events & EPOLLPRI) oss << "EPOLLPRI ";
  // available for write
  if (events & EPOLLOUT) oss << "EPOLLOUT ";
  // hang up happened on the fd
  if (events & EPOLLHUP) oss << "EPOLLHUP ";
  // peer closed connection, or shut down writing half of connection
  if (events & EPOLLRDHUP) oss << "EPOLLRDHUP ";
  // error condition happened (egg: peer closed)
  if (events & EPOLLERR) oss << "EPOLLERR ";
  // if (events & EPOLLNVAL) oss << "EPOLLNVAL ";

  return oss.str();
}

}  // namespace net
}  // namespace xlcomm